/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006-2009 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of XreaL source code.

XreaL source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

XreaL source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with XreaL source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
#include "client.h"

/*

key up events are sent even if in console mode

*/

field_t         historyEditLines[COMMAND_HISTORY];

int             nextHistoryLine;	// the last line in the history buffer, not masked
int             historyLine;	// the line being displayed from history buffer

							// will be <= nextHistoryLine

field_t         g_consoleField;
field_t         chatField;
qboolean        chat_team;

int             chat_playerNum;


qboolean        key_overstrikeMode;

int             anykeydown;
qkey_t          keys[MAX_KEYS];


typedef struct
{
	char           *name;
	int             keynum;
} keyname_t;


// names not in this list can either be lowercase ascii, or '0xnn' hex sequences
keyname_t       keynames[] = {
	{"TAB", K_TAB},
	{"ENTER", K_ENTER},
	{"ESCAPE", K_ESCAPE},
	{"SPACE", K_SPACE},
	{"BACKSPACE", K_BACKSPACE},
	{"UPARROW", K_UPARROW},
	{"DOWNARROW", K_DOWNARROW},
	{"LEFTARROW", K_LEFTARROW},
	{"RIGHTARROW", K_RIGHTARROW},

	{"ALT", K_ALT},
	{"CTRL", K_CTRL},
	{"SHIFT", K_SHIFT},

	{"COMMAND", K_COMMAND},

	{"CAPSLOCK", K_CAPSLOCK},


	{"F1", K_F1},
	{"F2", K_F2},
	{"F3", K_F3},
	{"F4", K_F4},
	{"F5", K_F5},
	{"F6", K_F6},
	{"F7", K_F7},
	{"F8", K_F8},
	{"F9", K_F9},
	{"F10", K_F10},
	{"F11", K_F11},
	{"F12", K_F12},
	{"F13", K_F13},
	{"F14", K_F14},
	{"F15", K_F15},

	{"INS", K_INS},
	{"DEL", K_DEL},
	{"PGDN", K_PGDN},
	{"PGUP", K_PGUP},
	{"HOME", K_HOME},
	{"END", K_END},

	{"MOUSE1", K_MOUSE1},
	{"MOUSE2", K_MOUSE2},
	{"MOUSE3", K_MOUSE3},
	{"MOUSE4", K_MOUSE4},
	{"MOUSE5", K_MOUSE5},

	{"MWHEELUP", K_MWHEELUP},
	{"MWHEELDOWN", K_MWHEELDOWN},

	{"JOY1", K_JOY1},
	{"JOY2", K_JOY2},
	{"JOY3", K_JOY3},
	{"JOY4", K_JOY4},
	{"JOY5", K_JOY5},
	{"JOY6", K_JOY6},
	{"JOY7", K_JOY7},
	{"JOY8", K_JOY8},
	{"JOY9", K_JOY9},
	{"JOY10", K_JOY10},
	{"JOY11", K_JOY11},
	{"JOY12", K_JOY12},
	{"JOY13", K_JOY13},
	{"JOY14", K_JOY14},
	{"JOY15", K_JOY15},
	{"JOY16", K_JOY16},
	{"JOY17", K_JOY17},
	{"JOY18", K_JOY18},
	{"JOY19", K_JOY19},
	{"JOY20", K_JOY20},
	{"JOY21", K_JOY21},
	{"JOY22", K_JOY22},
	{"JOY23", K_JOY23},
	{"JOY24", K_JOY24},
	{"JOY25", K_JOY25},
	{"JOY26", K_JOY26},
	{"JOY27", K_JOY27},
	{"JOY28", K_JOY28},
	{"JOY29", K_JOY29},
	{"JOY30", K_JOY30},
	{"JOY31", K_JOY31},
	{"JOY32", K_JOY32},

	{"AUX1", K_AUX1},
	{"AUX2", K_AUX2},
	{"AUX3", K_AUX3},
	{"AUX4", K_AUX4},
	{"AUX5", K_AUX5},
	{"AUX6", K_AUX6},
	{"AUX7", K_AUX7},
	{"AUX8", K_AUX8},
	{"AUX9", K_AUX9},
	{"AUX10", K_AUX10},
	{"AUX11", K_AUX11},
	{"AUX12", K_AUX12},
	{"AUX13", K_AUX13},
	{"AUX14", K_AUX14},
	{"AUX15", K_AUX15},
	{"AUX16", K_AUX16},

	{"KP_HOME", K_KP_HOME},
	{"KP_UPARROW", K_KP_UPARROW},
	{"KP_PGUP", K_KP_PGUP},
	{"KP_LEFTARROW", K_KP_LEFTARROW},
	{"KP_5", K_KP_5},
	{"KP_RIGHTARROW", K_KP_RIGHTARROW},
	{"KP_END", K_KP_END},
	{"KP_DOWNARROW", K_KP_DOWNARROW},
	{"KP_PGDN", K_KP_PGDN},
	{"KP_ENTER", K_KP_ENTER},
	{"KP_INS", K_KP_INS},
	{"KP_DEL", K_KP_DEL},
	{"KP_SLASH", K_KP_SLASH},
	{"KP_MINUS", K_KP_MINUS},
	{"KP_PLUS", K_KP_PLUS},
	{"KP_NUMLOCK", K_KP_NUMLOCK},
	{"KP_STAR", K_KP_STAR},
	{"KP_EQUALS", K_KP_EQUALS},

	{"PAUSE", K_PAUSE},

	{"SEMICOLON", ';'},			// because a raw semicolon seperates commands

	{"WORLD_0", K_WORLD_0},
	{"WORLD_1", K_WORLD_1},
	{"WORLD_2", K_WORLD_2},
	{"WORLD_3", K_WORLD_3},
	{"WORLD_4", K_WORLD_4},
	{"WORLD_5", K_WORLD_5},
	{"WORLD_6", K_WORLD_6},
	{"WORLD_7", K_WORLD_7},
	{"WORLD_8", K_WORLD_8},
	{"WORLD_9", K_WORLD_9},
	{"WORLD_10", K_WORLD_10},
	{"WORLD_11", K_WORLD_11},
	{"WORLD_12", K_WORLD_12},
	{"WORLD_13", K_WORLD_13},
	{"WORLD_14", K_WORLD_14},
	{"WORLD_15", K_WORLD_15},
	{"WORLD_16", K_WORLD_16},
	{"WORLD_17", K_WORLD_17},
	{"WORLD_18", K_WORLD_18},
	{"WORLD_19", K_WORLD_19},
	{"WORLD_20", K_WORLD_20},
	{"WORLD_21", K_WORLD_21},
	{"WORLD_22", K_WORLD_22},
	{"WORLD_23", K_WORLD_23},
	{"WORLD_24", K_WORLD_24},
	{"WORLD_25", K_WORLD_25},
	{"WORLD_26", K_WORLD_26},
	{"WORLD_27", K_WORLD_27},
	{"WORLD_28", K_WORLD_28},
	{"WORLD_29", K_WORLD_29},
	{"WORLD_30", K_WORLD_30},
	{"WORLD_31", K_WORLD_31},
	{"WORLD_32", K_WORLD_32},
	{"WORLD_33", K_WORLD_33},
	{"WORLD_34", K_WORLD_34},
	{"WORLD_35", K_WORLD_35},
	{"WORLD_36", K_WORLD_36},
	{"WORLD_37", K_WORLD_37},
	{"WORLD_38", K_WORLD_38},
	{"WORLD_39", K_WORLD_39},
	{"WORLD_40", K_WORLD_40},
	{"WORLD_41", K_WORLD_41},
	{"WORLD_42", K_WORLD_42},
	{"WORLD_43", K_WORLD_43},
	{"WORLD_44", K_WORLD_44},
	{"WORLD_45", K_WORLD_45},
	{"WORLD_46", K_WORLD_46},
	{"WORLD_47", K_WORLD_47},
	{"WORLD_48", K_WORLD_48},
	{"WORLD_49", K_WORLD_49},
	{"WORLD_50", K_WORLD_50},
	{"WORLD_51", K_WORLD_51},
	{"WORLD_52", K_WORLD_52},
	{"WORLD_53", K_WORLD_53},
	{"WORLD_54", K_WORLD_54},
	{"WORLD_55", K_WORLD_55},
	{"WORLD_56", K_WORLD_56},
	{"WORLD_57", K_WORLD_57},
	{"WORLD_58", K_WORLD_58},
	{"WORLD_59", K_WORLD_59},
	{"WORLD_60", K_WORLD_60},
	{"WORLD_61", K_WORLD_61},
	{"WORLD_62", K_WORLD_62},
	{"WORLD_63", K_WORLD_63},
	{"WORLD_64", K_WORLD_64},
	{"WORLD_65", K_WORLD_65},
	{"WORLD_66", K_WORLD_66},
	{"WORLD_67", K_WORLD_67},
	{"WORLD_68", K_WORLD_68},
	{"WORLD_69", K_WORLD_69},
	{"WORLD_70", K_WORLD_70},
	{"WORLD_71", K_WORLD_71},
	{"WORLD_72", K_WORLD_72},
	{"WORLD_73", K_WORLD_73},
	{"WORLD_74", K_WORLD_74},
	{"WORLD_75", K_WORLD_75},
	{"WORLD_76", K_WORLD_76},
	{"WORLD_77", K_WORLD_77},
	{"WORLD_78", K_WORLD_78},
	{"WORLD_79", K_WORLD_79},
	{"WORLD_80", K_WORLD_80},
	{"WORLD_81", K_WORLD_81},
	{"WORLD_82", K_WORLD_82},
	{"WORLD_83", K_WORLD_83},
	{"WORLD_84", K_WORLD_84},
	{"WORLD_85", K_WORLD_85},
	{"WORLD_86", K_WORLD_86},
	{"WORLD_87", K_WORLD_87},
	{"WORLD_88", K_WORLD_88},
	{"WORLD_89", K_WORLD_89},
	{"WORLD_90", K_WORLD_90},
	{"WORLD_91", K_WORLD_91},
	{"WORLD_92", K_WORLD_92},
	{"WORLD_93", K_WORLD_93},
	{"WORLD_94", K_WORLD_94},
	{"WORLD_95", K_WORLD_95},

	{"WINDOWS", K_SUPER},
	{"COMPOSE", K_COMPOSE},
	{"MODE", K_MODE},
	{"HELP", K_HELP},
	{"PRINT", K_PRINT},
	{"SYSREQ", K_SYSREQ},
	{"SCROLLOCK", K_SCROLLOCK},
	{"BREAK", K_BREAK},
	{"MENU", K_MENU},
	{"POWER", K_POWER},
	{"EURO", K_EURO},
	{"UNDO", K_UNDO},

	{"XBOX360_A", K_XBOX360_A},
	{"XBOX360_B", K_XBOX360_B},
	{"XBOX360_X", K_XBOX360_X},
	{"XBOX360_Y", K_XBOX360_Y},
	{"XBOX360_LB", K_XBOX360_LB},
	{"XBOX360_RB", K_XBOX360_RB},
	{"XBOX360_START", K_XBOX360_START},
	{"XBOX360_GUIDE", K_XBOX360_GUIDE},
	{"XBOX360_LS", K_XBOX360_LS},
	{"XBOX360_RS", K_XBOX360_RS},
	{"XBOX360_BACK", K_XBOX360_BACK},
	{"XBOX360_LT", K_XBOX360_LT},
	{"XBOX360_RT", K_XBOX360_RT},
	{"XBOX360_DPAD_UP", K_XBOX360_DPAD_UP},
	{"XBOX360_DPAD_RIGHT", K_XBOX360_DPAD_RIGHT},
	{"XBOX360_DPAD_DOWN", K_XBOX360_DPAD_DOWN},
	{"XBOX360_DPAD_LEFT", K_XBOX360_DPAD_LEFT},
	{"XBOX360_DPAD_RIGHTUP", K_XBOX360_DPAD_RIGHTUP},
	{"XBOX360_DPAD_RIGHTDOWN", K_XBOX360_DPAD_RIGHTDOWN},
	{"XBOX360_DPAD_LEFTUP", K_XBOX360_DPAD_LEFTUP},
	{"XBOX360_DPAD_LEFTDOWN", K_XBOX360_DPAD_LEFTDOWN},

	{NULL, 0}
};

/*
=============================================================================

EDIT FIELDS

=============================================================================
*/


/*
===================
Field_Draw

Handles horizontal scrolling and cursor blinking
x, y, and width are in pixels
===================
*/
void Field_VariableSizeDraw(field_t * edit, int x, int y, int width, int size, qboolean showCursor, qboolean noColorEscape)
{
	int             len;
	int             drawLen;
	int             prestep;
	char            str[MAX_STRING_CHARS];
	vec4_t          color;
	int             style;

	if(con_conshadow->value > 0)
		style = UI_DROPSHADOW;
	else
		style = 0;

	drawLen = edit->widthInChars - 1;	// - 1 so there is always a space for the cursor
	len = strlen(edit->buffer);

	// draw only inside given width
	if(drawLen * SMALLCHAR_WIDTH > width)
	{
		drawLen = width / SMALLCHAR_WIDTH;
	}

	// guarantee that cursor will be visible
	if(len <= drawLen)
	{
		prestep = 0;
	}
	else
	{
		if(edit->scroll + drawLen > len)
		{
			edit->scroll = len - drawLen;
			if(edit->scroll < 0)
			{
				edit->scroll = 0;
			}
		}
		prestep = edit->scroll;

		if(edit->cursor < len - drawLen)
		{
			prestep = edit->cursor;	// cursor at start
		}
		else
		{
			prestep = len - drawLen;
		}
	}

	if(prestep + drawLen > len)
	{
		drawLen = len - prestep;
	}

	// extract <drawLen> characters from the field at <prestep>
	if(drawLen >= MAX_STRING_CHARS)
	{
		Com_Error(ERR_DROP, "drawLen >= MAX_STRING_CHARS");
	}

	Com_Memcpy(str, edit->buffer + prestep, drawLen);
	str[drawLen] = 0;

	// draw it
	if(size == SMALLCHAR_WIDTH)
	{
		color[0] = color[1] = color[2] = color[3] = 1.0;
		SCR_Text_Paint(x, y, 0.15f, color, str, 0, 0, style, &cls.consoleFont);
	}
	else
	{
		color[0] = color[1] = color[2] = color[3] = 1.0;

		// draw big string with drop shadow
		SCR_Text_Paint(x, y, 0.25f, color, str, 0, 0, style, &cls.consoleFont);
	}

	// draw the cursor
	if(showCursor)
	{
		if((int)(cls.realtime >> 8) & 1)
		{
			return;				// off blink
		}

		if(key_overstrikeMode)
		{
			SCR_Text_Paint(x + (edit->cursor - prestep) * 4.5, y, 0.15f, color, "*", 0, 0, style, &cls.consoleFont);
		}
		else
		{
			SCR_Text_Paint(x + (edit->cursor - prestep) * 4.5, y, 0.15f, color, "_", 0, 0, style, &cls.consoleFont);
		}
	}
}

void Field_Draw(field_t * edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape)
{
	Field_VariableSizeDraw(edit, x, y, width, SMALLCHAR_WIDTH, showCursor, noColorEscape);
}

void Field_BigDraw(field_t * edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape)
{
	Field_VariableSizeDraw(edit, x, y, width, BIGCHAR_WIDTH, showCursor, noColorEscape);
}

/*
================
Field_Paste
================
*/
void Field_Paste(field_t * edit)
{
	char           *cbd;
	int             pasteLen, i;

	cbd = Sys_GetClipboardData();

	if(!cbd)
	{
		return;
	}

	// send as if typed, so insert / overstrike works properly
	pasteLen = strlen(cbd);
	for(i = 0; i < pasteLen; i++)
	{
		Field_CharEvent(edit, cbd[i]);
	}

	Z_Free(cbd);
}

/*
=================
Field_KeyDownEvent

Performs the basic line editing functions for the console,
in-game talk, and menu fields

Key events are used for non-printable characters, others are gotten from char events.
=================
*/
void Field_KeyDownEvent(field_t * edit, int key)
{
	int             len;

	// shift-insert is paste
	if(((key == K_INS) || (key == K_KP_INS)) && keys[K_SHIFT].down)
	{
		Field_Paste(edit);
		return;
	}

	key = tolower(key);
	len = strlen(edit->buffer);

	switch (key)
	{
		case K_DEL:
			if(edit->cursor < len)
			{
				memmove(edit->buffer + edit->cursor, edit->buffer + edit->cursor + 1, len - edit->cursor);
			}
			break;

		case K_RIGHTARROW:
			if(edit->cursor < len)
			{
				edit->cursor++;
			}
			break;

		case K_LEFTARROW:
			if(edit->cursor > 0)
			{
				edit->cursor--;
			}
			break;

		case K_HOME:
			edit->cursor = 0;
			break;

		case K_END:
			edit->cursor = len;
			break;

		case K_INS:
			key_overstrikeMode = !key_overstrikeMode;
			break;

		default:
			break;
	}

	// Change scroll if cursor is no longer visible
	if(edit->cursor < edit->scroll)
	{
		edit->scroll = edit->cursor;
	}
	else if(edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len)
	{
		edit->scroll = edit->cursor - edit->widthInChars + 1;
	}
}

/*
==================
Field_CharEvent
==================
*/
void Field_CharEvent(field_t * edit, int ch)
{
	int             len;

	if(ch == 'v' - 'a' + 1)
	{							// ctrl-v is paste
		Field_Paste(edit);
		return;
	}

	if(ch == 'c' - 'a' + 1)
	{							// ctrl-c clears the field
		Field_Clear(edit);
		return;
	}

	len = strlen(edit->buffer);

	if(ch == 'h' - 'a' + 1)
	{							// ctrl-h is backspace
		if(edit->cursor > 0)
		{
			memmove(edit->buffer + edit->cursor - 1, edit->buffer + edit->cursor, len + 1 - edit->cursor);
			edit->cursor--;
			if(edit->cursor < edit->scroll)
			{
				edit->scroll--;
			}
		}
		return;
	}

	if(ch == 'a' - 'a' + 1)
	{							// ctrl-a is home
		edit->cursor = 0;
		edit->scroll = 0;
		return;
	}

	if(ch == 'e' - 'a' + 1)
	{							// ctrl-e is end
		edit->cursor = len;
		edit->scroll = edit->cursor - edit->widthInChars;
		return;
	}

	//
	// ignore any other non printable chars
	//
	if(ch < 32)
	{
		return;
	}

	if(key_overstrikeMode)
	{
		if(edit->cursor == MAX_EDIT_LINE - 1)
			return;
		edit->buffer[edit->cursor] = ch;
		edit->cursor++;
	}
	else
	{							// insert mode
		if(len == MAX_EDIT_LINE - 1)
		{
			return;				// all full
		}
		memmove(edit->buffer + edit->cursor + 1, edit->buffer + edit->cursor, len + 1 - edit->cursor);
		edit->buffer[edit->cursor] = ch;
		edit->cursor++;
	}


	if(edit->cursor >= edit->widthInChars)
	{
		edit->scroll++;
	}

	if(edit->cursor == len + 1)
	{
		edit->buffer[edit->cursor] = 0;
	}
}

/*
=============================================================================

CONSOLE LINE EDITING

==============================================================================
*/

/*
====================
Console_Key

Handles history and console scrollback
====================
*/
void Console_Key(int key)
{
	// ctrl-L clears screen
	if(key == 'l' && keys[K_CTRL].down)
	{
		Cbuf_AddText("clear\n");
		return;
	}

	// enter finishes the line
	if(key == K_ENTER || key == K_KP_ENTER)
	{
		// if not in the game explicitly prepend a slash if needed
		if(cls.state != CA_ACTIVE && g_consoleField.buffer[0] != '\\' && g_consoleField.buffer[0] != '/')
		{
			char            temp[MAX_EDIT_LINE - 1];

			Q_strncpyz(temp, g_consoleField.buffer, sizeof(temp));
			Com_sprintf(g_consoleField.buffer, sizeof(g_consoleField.buffer), "\\%s", temp);
			g_consoleField.cursor++;
		}

		Com_Printf("]%s\n", g_consoleField.buffer);

		// leading slash is an explicit command
		if(g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/')
		{
			Cbuf_AddText(g_consoleField.buffer + 1);	// valid command
			Cbuf_AddText("\n");
		}
		else
		{
			// other text will be chat messages
			if(!g_consoleField.buffer[0])
			{
				return;			// empty lines just scroll the console without adding to history
			}
			else
			{
				Cbuf_AddText("cmd say ");
				Cbuf_AddText(g_consoleField.buffer);
				Cbuf_AddText("\n");
			}
		}

		// copy line to history buffer
		historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField;
		nextHistoryLine++;
		historyLine = nextHistoryLine;

		Field_Clear(&g_consoleField);

		g_consoleField.widthInChars = g_console_field_width;

		CL_SaveConsoleHistory();

		if(cls.state == CA_DISCONNECTED)
		{
			SCR_UpdateScreen();	// force an update, because the command
		}						// may take some time
		return;
	}

	// command completion

	if(key == K_TAB)
	{
		Field_AutoComplete(&g_consoleField);
		return;
	}

	// command history (ctrl-p ctrl-n for unix style)

	if((key == K_MWHEELUP && keys[K_SHIFT].down) || (key == K_UPARROW) || (key == K_KP_UPARROW) ||
	   ((tolower(key) == 'p') && keys[K_CTRL].down))
	{
		if(nextHistoryLine - historyLine < COMMAND_HISTORY && historyLine > 0)
		{
			historyLine--;
		}
		g_consoleField = historyEditLines[historyLine % COMMAND_HISTORY];
		return;
	}

	if((key == K_MWHEELDOWN && keys[K_SHIFT].down) || (key == K_DOWNARROW) || (key == K_KP_DOWNARROW) ||
	   ((tolower(key) == 'n') && keys[K_CTRL].down))
	{
		historyLine++;
		if(historyLine >= nextHistoryLine)
		{
			historyLine = nextHistoryLine;
			Field_Clear(&g_consoleField);
			g_consoleField.widthInChars = g_console_field_width;
			return;
		}
		g_consoleField = historyEditLines[historyLine % COMMAND_HISTORY];
		return;
	}

	// console scrolling
	if(key == K_PGUP)
	{
		Con_PageUp();
		return;
	}

	if(key == K_PGDN)
	{
		Con_PageDown();
		return;
	}

	if(key == K_MWHEELUP)
	{							//----(SA)  added some mousewheel functionality to the console
		Con_PageUp();
		if(keys[K_CTRL].down)
		{						// hold <ctrl> to accelerate scrolling
			Con_PageUp();
			Con_PageUp();
		}
		return;
	}

	if(key == K_MWHEELDOWN)
	{							//----(SA)  added some mousewheel functionality to the console
		Con_PageDown();
		if(keys[K_CTRL].down)
		{						// hold <ctrl> to accelerate scrolling
			Con_PageDown();
			Con_PageDown();
		}
		return;
	}

	// ctrl-home = top of console
	if(key == K_HOME && keys[K_CTRL].down)
	{
		Con_Top();
		return;
	}

	// ctrl-end = bottom of console
	if(key == K_END && keys[K_CTRL].down)
	{
		Con_Bottom();
		return;
	}

	// pass to the normal editline routine
	Field_KeyDownEvent(&g_consoleField, key);
}

//============================================================================


/*
================
Message_Key

In game talk message
================
*/
void Message_Key(int key)
{

	char            buffer[MAX_STRING_CHARS];


	if(key == K_ESCAPE)
	{
		Key_SetCatcher(Key_GetCatcher() & ~KEYCATCH_MESSAGE);
		Field_Clear(&chatField);
		return;
	}

	if(key == K_ENTER || key == K_KP_ENTER)
	{
		if(chatField.buffer[0] && cls.state == CA_ACTIVE)
		{
			if(chat_playerNum != -1)

				Com_sprintf(buffer, sizeof(buffer), "tell %i \"%s\"\n", chat_playerNum, chatField.buffer);

			else if(chat_team)

				Com_sprintf(buffer, sizeof(buffer), "say_team \"%s\"\n", chatField.buffer);
			else
				Com_sprintf(buffer, sizeof(buffer), "say \"%s\"\n", chatField.buffer);



			CL_AddReliableCommand(buffer, qfalse);
		}
		Key_SetCatcher(Key_GetCatcher() & ~KEYCATCH_MESSAGE);
		Field_Clear(&chatField);
		return;
	}

	Field_KeyDownEvent(&chatField, key);
}

//============================================================================


qboolean Key_GetOverstrikeMode(void)
{
	return key_overstrikeMode;
}


void Key_SetOverstrikeMode(qboolean state)
{
	key_overstrikeMode = state;
}


/*
===================
Key_IsDown
===================
*/
qboolean Key_IsDown(int keynum)
{
	if(keynum < 0 || keynum >= MAX_KEYS)
	{
		return qfalse;
	}

	return keys[keynum].down;
}

/*
===================
Key_StringToKeynum

Returns a key number to be used to index keys[] by looking at
the given string.  Single ascii characters return themselves, while
the K_* names are matched up.

0x11 will be interpreted as raw hex, which will allow new controlers

to be configured even if they don't have defined names.
===================
*/
int Key_StringToKeynum(char *str)
{
	keyname_t      *kn;

	if(!str || !str[0])
	{
		return -1;
	}
	if(!str[1])
	{
		return str[0];
	}

	// check for hex code
	if(strlen(str) == 4)
	{
		int             n = Com_HexStrToInt(str);

		if(n >= 0)
		{
			return n;
		}
	}

	// scan for a text match
	for(kn = keynames; kn->name; kn++)
	{
		if(!Q_stricmp(str, kn->name))
			return kn->keynum;
	}

	return -1;
}

/*
===================
Key_KeynumToString

Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the
given keynum.
===================
*/
char           *Key_KeynumToString(int keynum)
{
	keyname_t      *kn;
	static char     tinystr[5];
	int             i, j;

	if(keynum == -1)
	{
		return "<KEY NOT FOUND>";
	}

	if(keynum < 0 || keynum >= MAX_KEYS)
	{
		return "<OUT OF RANGE>";
	}

	// check for printable ascii (don't use quote)
	if(keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';')
	{
		tinystr[0] = keynum;
		tinystr[1] = 0;
		return tinystr;
	}

	// check for a key string
	for(kn = keynames; kn->name; kn++)
	{
		if(keynum == kn->keynum)
		{
			return kn->name;
		}
	}

	// make a hex string
	i = keynum >> 4;
	j = keynum & 15;

	tinystr[0] = '0';
	tinystr[1] = 'x';
	tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0';
	tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0';
	tinystr[4] = 0;

	return tinystr;
}


/*
===================
Key_SetBinding
===================
*/
void Key_SetBinding(int keynum, const char *binding)
{
	if(keynum < 0 || keynum >= MAX_KEYS)
	{
		return;
	}

	// free old bindings
	if(keys[keynum].binding)
	{
		Z_Free(keys[keynum].binding);
	}

	// allocate memory for new binding
	keys[keynum].binding = CopyString(binding);

	// consider this like modifying an archived cvar, so the
	// file write will be triggered at the next oportunity
	cvar_modifiedFlags |= CVAR_ARCHIVE;
}


/*
===================
Key_GetBinding
===================
*/
char           *Key_GetBinding(int keynum)
{
	if(keynum < 0 || keynum >= MAX_KEYS)
	{
		return "";
	}

	return keys[keynum].binding;
}

/*
===================
Key_GetKey
===================
*/

int Key_GetKey(const char *binding)
{
	int             i;

	if(binding)
	{
		for(i = 0; i < MAX_KEYS; i++)
		{
			if(keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0)
			{
				return i;
			}
		}
	}
	return -1;
}

/*
===================
Key_Unbind_f
===================
*/
void Key_Unbind_f(void)
{
	int             b;

	if(Cmd_Argc() != 2)
	{
		Com_Printf("unbind <key> : remove commands from a key\n");
		return;
	}

	b = Key_StringToKeynum(Cmd_Argv(1));
	if(b == -1)
	{
		Com_Printf("\"%s\" isn't a valid key\n", Cmd_Argv(1));
		return;
	}

	Key_SetBinding(b, "");
}

/*
===================
Key_Unbindall_f
===================
*/
void Key_Unbindall_f(void)
{
	int             i;

	for(i = 0; i < MAX_KEYS; i++)
		if(keys[i].binding)
			Key_SetBinding(i, "");
}


/*
===================
Key_Bind_f
===================
*/
void Key_Bind_f(void)
{
	int             i, c, b;
	char            cmd[1024];

	c = Cmd_Argc();

	if(c < 2)
	{
		Com_Printf("bind <key> [command] : attach a command to a key\n");
		return;
	}
	b = Key_StringToKeynum(Cmd_Argv(1));
	if(b == -1)
	{
		Com_Printf("\"%s\" isn't a valid key\n", Cmd_Argv(1));
		return;
	}

	if(c == 2)
	{
		if(keys[b].binding)
			Com_Printf("\"%s\" = \"%s\"\n", Cmd_Argv(1), keys[b].binding);
		else
			Com_Printf("\"%s\" is not bound\n", Cmd_Argv(1));
		return;
	}

// copy the rest of the command line
	cmd[0] = 0;					// start out with a null string
	for(i = 2; i < c; i++)
	{
		strcat(cmd, Cmd_Argv(i));
		if(i != (c - 1))
			strcat(cmd, " ");
	}

	Key_SetBinding(b, cmd);
}

/*
============
Key_WriteBindings

Writes lines containing "bind key value"
============
*/
void Key_WriteBindings(fileHandle_t f)
{
	int             i;

	FS_Printf(f, "unbindall\n");

	for(i = 0; i < MAX_KEYS; i++)
	{
		if(keys[i].binding && keys[i].binding[0])
		{
			FS_Printf(f, "bind %s \"%s\"\n", Key_KeynumToString(i), keys[i].binding);

		}

	}
}


/*
============
Key_Bindlist_f

============
*/
void Key_Bindlist_f(void)
{
	int             i;

	for(i = 0; i < MAX_KEYS; i++)
	{
		if(keys[i].binding && keys[i].binding[0])
		{
			Com_Printf("%s \"%s\"\n", Key_KeynumToString(i), keys[i].binding);
		}
	}
}

/*
============
Key_KeynameCompletion
============
*/
void Key_KeynameCompletion(void (*callback) (const char *s))
{
	int             i;

	for(i = 0; keynames[i].name != NULL; i++)
		callback(keynames[i].name);
}

/*
====================
Key_CompleteUnbind
====================
*/
static void Key_CompleteUnbind(char *args, int argNum)
{
	if(argNum == 2)
	{
		// Skip "unbind "
		char           *p = Com_SkipTokens(args, 1, " ");

		if(p > args)
			Field_CompleteKeyname();
	}
}

/*
====================
Key_CompleteBind
====================
*/
static void Key_CompleteBind(char *args, int argNum)
{
	char           *p;

	if(argNum == 2)
	{
		// Skip "bind "
		p = Com_SkipTokens(args, 1, " ");

		if(p > args)
			Field_CompleteKeyname();
	}
	else if(argNum >= 3)
	{
		// Skip "bind <key> "
		p = Com_SkipTokens(args, 2, " ");

		if(p > args)
			Field_CompleteCommand(p, qtrue, qtrue);
	}
}

/*
===================
CL_InitKeyCommands
===================
*/
void CL_InitKeyCommands(void)
{
	// register our functions
	Cmd_AddCommand("bind", Key_Bind_f);
	Cmd_SetCommandCompletionFunc("bind", Key_CompleteBind);
	Cmd_AddCommand("unbind", Key_Unbind_f);
	Cmd_SetCommandCompletionFunc("unbind", Key_CompleteUnbind);
	Cmd_AddCommand("unbindall", Key_Unbindall_f);
	Cmd_AddCommand("bindlist", Key_Bindlist_f);
}

/*
===================
CL_ParseBinding

Execute the commands in the bind string
===================
*/
void CL_ParseBinding(int key, qboolean down, unsigned time)
{
	char            buf[MAX_STRING_CHARS], *p = buf, *end;

	if(!keys[key].binding || !keys[key].binding[0])
		return;
	Q_strncpyz(buf, keys[key].binding, sizeof(buf));

	while(1)
	{
		while(isspace(*p))
			p++;
		end = strchr(p, ';');
		if(end)
			*end = '\0';
		if(*p == '+')
		{
			// button commands add keynum and time as parameters
			// so that multiple sources can be discriminated and
			// subframe corrected
			char            cmd[1024];

			Com_sprintf(cmd, sizeof(cmd), "%c%s %d %d\n", (down) ? '+' : '-', p + 1, key, time);
			Cbuf_AddText(cmd);
		}
		else if(down)
		{
			// normal commands only execute on key press
			Cbuf_AddText(p);
			Cbuf_AddText("\n");
		}
		if(!end)
			break;
		p = end + 1;
	}
}

/*
===================
CL_KeyDownEvent

Called by CL_KeyEvent to handle a keypress
===================
*/
void CL_KeyDownEvent(int key, unsigned time)
{
	keys[key].down = qtrue;
	keys[key].repeats++;
	if(keys[key].repeats == 1)
		anykeydown++;

	if(keys[K_ALT].down && key == K_ENTER)
	{
		Cvar_SetValue("r_fullscreen", !Cvar_VariableIntegerValue("r_fullscreen"));
		return;
	}

	// console key is hardcoded, so the user can never unbind it
	if(key == K_CONSOLE || (keys[K_SHIFT].down && key == K_ESCAPE))
	{
		Con_ToggleConsole_f();
		Key_ClearStates();
		return;
	}


	// keys can still be used for bound actions
	if((key < 128 || key == K_MOUSE1) && (clc.demoplaying || cls.state == CA_CINEMATIC) && Key_GetCatcher() == 0)
	{

		if(Cvar_VariableValue("com_cameraMode") == 0)
		{
			Cvar_Set("nextdemo", "");
			key = K_ESCAPE;
		}
	}

	// escape is always handled special
	if(key == K_ESCAPE)
	{
		if(Key_GetCatcher() & KEYCATCH_MESSAGE)
		{
			// clear message mode
			Message_Key(key);
			return;
		}

		// escape always gets out of CGAME stuff
		if(Key_GetCatcher() & KEYCATCH_CGAME)
		{
			Key_SetCatcher(Key_GetCatcher() & ~KEYCATCH_CGAME);
#if defined(USE_JAVA)
			Java_CG_EventHandling(CGAME_EVENT_NONE);
#else
			VM_Call(cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE);
#endif
			return;
		}

		if(!(Key_GetCatcher() & KEYCATCH_UI))
		{
			if(cls.state == CA_ACTIVE && !clc.demoplaying)
			{
#if defined(USE_JAVA)
				Java_UI_SetActiveMenu(UIMENU_INGAME);
#else
				VM_Call(uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME);
#endif
			}
			else if(cls.state != CA_DISCONNECTED)
			{
				CL_Disconnect_f();
				S_StopAllSounds();
#if defined(USE_JAVA)
				Java_UI_SetActiveMenu(UIMENU_MAIN);
#else
				VM_Call(uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN);
#endif
			}
			return;
		}

#if defined(USE_JAVA)
		Java_UI_KeyEvent(key, qtrue);
#else
		VM_Call(uivm, UI_KEY_EVENT, key, qtrue);
#endif
		return;
	}

	// distribute the key down event to the apropriate handler
	if(Key_GetCatcher() & KEYCATCH_CONSOLE)
	{
		Console_Key(key);
	}
	else if(Key_GetCatcher() & KEYCATCH_UI)
	{
#if defined(USE_JAVA)
		Java_UI_KeyEvent(key, qtrue);
#else
		if(uivm)
		{
			VM_Call(uivm, UI_KEY_EVENT, key, qtrue);
		}
#endif
	}
	else if(Key_GetCatcher() & KEYCATCH_CGAME)
	{
#if defined(USE_JAVA)
		Java_CG_KeyEvent(key, qtrue);
#else
		if(cgvm)
		{
			VM_Call(cgvm, CG_KEY_EVENT, key, qtrue);
		}
#endif
	}
	else if(Key_GetCatcher() & KEYCATCH_MESSAGE)
	{
		Message_Key(key);
	}
	else if(cls.state == CA_DISCONNECTED)
	{
		Console_Key(key);
	}
	else
	{
		// send the bound action
		CL_ParseBinding(key, qtrue, time);
	}
	return;
}

/*
===================
CL_KeyUpEvent

Called by CL_KeyEvent to handle a keyrelease
===================
*/
void CL_KeyUpEvent(int key, unsigned time)
{
	keys[key].repeats = 0;
	keys[key].down = qfalse;
	anykeydown--;
	if(anykeydown < 0)
	{
		anykeydown = 0;
	}

	// don't process key-up events for the console key
	if(key == K_CONSOLE || (key == K_ESCAPE && keys[K_SHIFT].down))
		return;

	//
	// key up events only perform actions if the game key binding is
	// a button command (leading + sign).  These will be processed even in
	// console mode and menu mode, to keep the character from continuing
	// an action started before a mode switch.
	//
	if(cls.state != CA_DISCONNECTED)
		CL_ParseBinding(key, qfalse, time);

	if(Key_GetCatcher() & KEYCATCH_UI)
	{
#if defined(USE_JAVA)
		Java_UI_KeyEvent(key, qfalse);
#else
		if(uivm)
		{
			VM_Call(uivm, UI_KEY_EVENT, key, qfalse);
		}
#endif
	}
	else if(Key_GetCatcher() & KEYCATCH_CGAME)
	{
#if defined(USE_JAVA)
		Java_CG_KeyEvent(key, qfalse);
#else
		if(cgvm)
		{
			VM_Call(cgvm, CG_KEY_EVENT, key, qfalse);
		}
#endif
	}
}

/*
===================
CL_KeyEvent

Called by the system for both key up and key down events
===================
*/
void CL_KeyEvent(int key, qboolean down, unsigned time)
{
	if(down)
		CL_KeyDownEvent(key, time);
	else
		CL_KeyUpEvent(key, time);
}

/*
===================
CL_CharEvent

Normal keyboard characters, already shifted / capslocked / etc
===================
*/
void CL_CharEvent(int key)
{
	// delete is not a printable character and is
	// otherwise handled by Field_KeyDownEvent
	if(key == 127)
	{
		return;
	}

	// distribute the key down event to the apropriate handler
	if(Key_GetCatcher() & KEYCATCH_CONSOLE)
	{
		Field_CharEvent(&g_consoleField, key);
	}
	else if(Key_GetCatcher() & KEYCATCH_UI)
	{
#if defined(USE_JAVA)
		Java_UI_KeyEvent(key | K_CHAR_FLAG, qtrue);
#else
		VM_Call(uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue);
#endif
	}
	else if(Key_GetCatcher() & KEYCATCH_MESSAGE)
	{
		Field_CharEvent(&chatField, key);
	}
	else if(cls.state == CA_DISCONNECTED)
	{
		Field_CharEvent(&g_consoleField, key);
	}
}


/*
===================
Key_ClearStates
===================
*/
void Key_ClearStates(void)
{
	int             i;

	anykeydown = 0;

	for(i = 0; i < MAX_KEYS; i++)
	{
		if(keys[i].down)
		{
			CL_KeyEvent(i, qfalse, 0);

		}
		keys[i].down = 0;
		keys[i].repeats = 0;
	}
}

static int      keyCatchers = 0;

/*
====================
Key_GetCatcher
====================
*/
int Key_GetCatcher(void)
{
	return keyCatchers;
}

/*
====================
Key_SetCatcher
====================
*/
void Key_SetCatcher(int catcher)
{
	// If the catcher state is changing, clear all key states
	if(catcher != keyCatchers)
		Key_ClearStates();

	keyCatchers = catcher;
}

// This must not exceed MAX_CMD_LINE
#define			MAX_CONSOLE_SAVE_BUFFER	1024
#define			CONSOLE_HISTORY_FILE    "q3history"
static char     consoleSaveBuffer[MAX_CONSOLE_SAVE_BUFFER];
static int      consoleSaveBufferSize = 0;

/*
================
CL_LoadConsoleHistory

Load the console history from cl_consoleHistory
================
*/
void CL_LoadConsoleHistory(void)
{
	char           *token, *text_p;
	int             i, numChars, numLines = 0;
	fileHandle_t    f;

	consoleSaveBufferSize = FS_FOpenFileRead(CONSOLE_HISTORY_FILE, &f, qfalse);
	if(!f)
	{
		Com_Printf("Couldn't read %s.\n", CONSOLE_HISTORY_FILE);
		return;
	}

	if(consoleSaveBufferSize <= MAX_CONSOLE_SAVE_BUFFER &&
	   FS_Read(consoleSaveBuffer, consoleSaveBufferSize, f) == consoleSaveBufferSize)
	{
		text_p = consoleSaveBuffer;

		for(i = COMMAND_HISTORY - 1; i >= 0; i--)
		{
			if(!*(token = Com_Parse(&text_p)))
				break;

			historyEditLines[i].cursor = atoi(token);

			if(!*(token = Com_Parse(&text_p)))
				break;

			historyEditLines[i].scroll = atoi(token);

			if(!*(token = Com_Parse(&text_p)))
				break;

			numChars = atoi(token);
			text_p++;
			if(numChars > (strlen(consoleSaveBuffer) - (text_p - consoleSaveBuffer)))
			{
				Com_DPrintf(S_COLOR_YELLOW "WARNING: probable corrupt history\n");
				break;
			}
			Com_Memcpy(historyEditLines[i].buffer, text_p, numChars);
			historyEditLines[i].buffer[numChars] = '\0';
			text_p += numChars;

			numLines++;
		}

		memmove(&historyEditLines[0], &historyEditLines[i + 1], numLines * sizeof(field_t));
		for(i = numLines; i < COMMAND_HISTORY; i++)
			Field_Clear(&historyEditLines[i]);

		historyLine = nextHistoryLine = numLines;
	}
	else
		Com_Printf("Couldn't read %s.\n", CONSOLE_HISTORY_FILE);

	FS_FCloseFile(f);
}

/*
================
CL_SaveConsoleHistory

Save the console history into the cvar cl_consoleHistory
so that it persists across invocations of q3
================
*/
void CL_SaveConsoleHistory(void)
{
	int             i;
	int             lineLength, saveBufferLength, additionalLength;
	fileHandle_t    f;

	consoleSaveBuffer[0] = '\0';

	i = (nextHistoryLine - 1) % COMMAND_HISTORY;
	do
	{
		if(historyEditLines[i].buffer[0])
		{
			lineLength = strlen(historyEditLines[i].buffer);
			saveBufferLength = strlen(consoleSaveBuffer);

			//ICK
			additionalLength = lineLength + strlen("999 999 999  ");

			if(saveBufferLength + additionalLength < MAX_CONSOLE_SAVE_BUFFER)
			{
				Q_strcat(consoleSaveBuffer, MAX_CONSOLE_SAVE_BUFFER,
						 va("%d %d %d %s ",
							historyEditLines[i].cursor, historyEditLines[i].scroll, lineLength, historyEditLines[i].buffer));
			}
			else
				break;
		}
		i = (i - 1 + COMMAND_HISTORY) % COMMAND_HISTORY;
	}
	while(i != (nextHistoryLine - 1) % COMMAND_HISTORY);

	consoleSaveBufferSize = strlen(consoleSaveBuffer);

	f = FS_FOpenFileWrite(CONSOLE_HISTORY_FILE);
	if(!f)
	{
		Com_Printf("Couldn't write %s.\n", CONSOLE_HISTORY_FILE);
		return;
	}

	if(FS_Write(consoleSaveBuffer, consoleSaveBufferSize, f) < consoleSaveBufferSize)
		Com_Printf("Couldn't write %s.\n", CONSOLE_HISTORY_FILE);

	FS_FCloseFile(f);
}
